#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cpufreq.h>
#include <linux/thermal.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/cpufreq.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include "sunxi-budget-cooling.h"
#include "sunxi-budget-interface.h"

#define SUNXI_BUDGET_COOLING_NAME "sunxi-budget"
#define SUNXI_BUDGET_DRIVER_NAME "sunxi-budget-cooling"

static struct sunxi_budget_cooling_device *budget_cdev;

#define USERSPACE_ROOMAGE
#ifdef USERSPACE_ROOMAGE
static ssize_t
sunxi_budget_roomage_show(struct device *dev,
					struct device_attribute *attr, char *buf)
{
	int ret=0,i;
	unsigned int roomage_data[8] = {0};
	struct sunxi_budget_cooling_device *budget_cdev = dev_get_drvdata(dev);

	if(!budget_cdev)
		return ret;
	for(i = 0; i < budget_cdev->cluster_num; i++){
		sunxi_cpufreq_get_roomage(budget_cdev, &roomage_data[2 * i],
				&roomage_data[2 * i + 4], i);
		sunxi_hotplug_get_roomage(budget_cdev, &roomage_data[2 * i + 1],
				&roomage_data[2 * i + 5], i);
	}

	ret += sprintf(buf, "roomage:%d,%d,%d,%d,%d,%d,%d,%d\n",
				roomage_data[0],
				roomage_data[1],
				roomage_data[2],
				roomage_data[3],
				roomage_data[4],
				roomage_data[5],
				roomage_data[6],
				roomage_data[7]);
	return ret;
}

static ssize_t
sunxi_budget_roomage_store(struct device *dev, struct device_attribute *attr,
					const char *buf, size_t count)
{
	int i;
	unsigned int roomage_data[8] = {0};
	unsigned int pre_freq_floor = 0, pre_freq_roof = 0, next_freq_floor = 0, next_freq_roof = 0;
	unsigned int pre_num_floor = 0, pre_num_roof = 0, next_num_floor = 0, next_num_roof = 0;
	struct sunxi_budget_cooling_device *budget_cdev = dev_get_drvdata(dev);

	if(!budget_cdev)
		return count;

	sscanf(buf,"%u %u %u %u %u %u %u %u\n",
			&roomage_data[0],
			&roomage_data[1],
			&roomage_data[2],
			&roomage_data[3],
			&roomage_data[4],
			&roomage_data[5],
			&roomage_data[6],
			&roomage_data[7]);
	for(i = 0; i < budget_cdev->cluster_num; i++){
		sunxi_cpufreq_get_roomage(budget_cdev, &pre_freq_floor, &pre_freq_roof, i);
		sunxi_hotplug_get_roomage(budget_cdev, &pre_num_floor, &pre_num_roof, i);
		next_freq_floor = roomage_data[2 * i];
		next_num_floor = roomage_data[2 * i + 1];
		next_freq_roof = roomage_data[2 * i + 4];
		next_num_roof = roomage_data[2 *i + 5];

		if(next_freq_roof < pre_freq_roof && next_num_roof > pre_num_roof){
			sunxi_cpufreq_set_roomage(budget_cdev, next_freq_floor, next_freq_roof, i);
			sunxi_hotplug_set_roomage(budget_cdev, next_num_floor, next_num_roof, i);
		}else{
			sunxi_hotplug_set_roomage(budget_cdev, next_num_floor, next_num_roof, i);
			sunxi_cpufreq_set_roomage(budget_cdev, next_freq_floor, next_freq_roof, i);
		}
	}

	return count;
}
static DEVICE_ATTR(roomage, 0644,sunxi_budget_roomage_show, sunxi_budget_roomage_store);
#endif

static int cpu_budget_apply_cooling(struct sunxi_budget_cooling_device *cooling_device,
				unsigned long cooling_state)
{
	int ret = 0,cluster;
	u32 pre_state;
	u32 pre_freq = 0, next_freq = 0, pre_num = 0, next_num = 0;

	/* Check if the old cooling action is same as new cooling action */
	if (cooling_device->cooling_state == cooling_state)
		return 0;

	if(cooling_state >= cooling_device->state_num)
		return -EINVAL;
	pre_state = cooling_device->cooling_state;
	cooling_device->cooling_state = cooling_state;

	for(cluster = 0; cluster < cooling_device->cluster_num; cluster++){
		if(cooling_device->cpufreq){
			pre_freq = cooling_device->cpufreq->tbl[pre_state].cluster_freq[cluster];
			next_freq = cooling_device->cpufreq->tbl[cooling_state].cluster_freq[cluster];
		}
		if(cooling_device->hotplug){
			pre_num = cooling_device->hotplug->tbl[pre_state].cluster_num[cluster];
			next_num = cooling_device->hotplug->tbl[cooling_state].cluster_num[cluster];
		}

		if((pre_freq < next_freq) && (pre_num > next_num)){
			ret = sunxi_hotplug_update_state(cooling_device, cluster);
			if(ret)
				return ret;
			ret = sunxi_cpufreq_update_state(cooling_device, cluster);
			if(ret)
				return ret;
		}else{
			ret = sunxi_cpufreq_update_state(cooling_device, cluster);
			if(ret)
				return ret;
			ret = sunxi_hotplug_update_state(cooling_device, cluster);
			if(ret)
				return ret;
		}
	}

	return 0;
}

/**
 * cpu_budget_get_max_state - callback function to get the max cooling state.
 * @cdev: thermal cooling device pointer.
 * @state: fill this variable with the max cooling state.
 */
static int cpu_budget_get_max_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	struct sunxi_budget_cooling_device *cooling_device = cdev->devdata;
	*state = cooling_device->state_num-1;
	return 0;
}

/**
 * cpu_budget_get_cur_state - callback function to get the current cooling state.
 * @cdev: thermal cooling device pointer.
 * @state: fill this variable with the current cooling state.
 */
static int cpu_budget_get_cur_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	struct sunxi_budget_cooling_device *cooling_device = cdev->devdata;
	*state = cooling_device->cooling_state;
	return 0;
}

/**
 * cpu_budget_set_cur_state - callback function to set the current cooling state.
 * @cdev: thermal cooling device pointer.
 * @state: set this variable to the current cooling state.
 */
static int cpu_budget_set_cur_state(struct thermal_cooling_device *cdev,
				 unsigned long state)
{
	struct sunxi_budget_cooling_device *cooling_device = cdev->devdata;
	return cpu_budget_apply_cooling(cooling_device, state);
}

/* Bind cpufreq callbacks to thermal cooling device ops */
static struct thermal_cooling_device_ops const sunxi_cpu_cooling_ops = {
	.get_max_state = cpu_budget_get_max_state,
	.get_cur_state = cpu_budget_get_cur_state,
	.set_cur_state = cpu_budget_set_cur_state,
};

/**
 * sunxi_budget_cooling_get_cpumask - get the cpumask of system.
 */
static int sunxi_budget_cooling_get_cpumask(void)
{
	int cluster,i;
	unsigned int min = 0, max = 0;
	struct cpufreq_policy policy;
#if defined(CONFIG_SCHED_HMP)
	arch_get_fast_and_slow_cpus(&budget_cdev->cluster_cpus[1],&budget_cdev->cluster_cpus[0]);
#elif defined(CONFIG_SCHED_SMP_DCMP)
	if (strlen(CONFIG_CLUSTER0_CPU_MASK) && strlen(CONFIG_CLUSTER1_CPU_MASK)) {
		if (cpulist_parse(CONFIG_CLUSTER0_CPU_MASK, &budget_cdev->cluster_cpus[0])) {
			pr_err("Failed to parse cluster0 cpu mask!\n");
			return -EBUSY;
		}
		if (cpulist_parse(CONFIG_CLUSTER1_CPU_MASK, &budget_cdev->cluster_cpus[1])) {
			pr_err("Failed to parse cluster1 cpu mask!\n");
			return -EBUSY;
		}
	}
#else
	cpumask_copy(&budget_cdev->cluster_cpus[0], cpu_possible_mask);
	budget_cdev->cluster_num = 1;
#endif
	for(cluster = 0; cluster < budget_cdev->cluster_num; min = 0, max = 0, cluster ++){
		for_each_cpu(i, &budget_cdev->cluster_cpus[cluster]) {
			/*continue if cpufreq policy not found and not return error*/
			if (!cpufreq_get_policy(&policy, i))
				continue;
			if (min == 0 && max == 0) {
				min = policy.cpuinfo.min_freq;
				max = policy.cpuinfo.max_freq;
			} else {
				if (min != policy.cpuinfo.min_freq ||
					max != policy.cpuinfo.max_freq){
						pr_err("different freq, return.\n");
						return -EBUSY;
					}
			}
		}
	}
	return 0;
}

static int sunxi_budget_cooling_parse(struct platform_device *pdev)
{
	struct device_node *np = NULL;
	int ret = 0;

	np = pdev->dev.of_node;

	if (!of_device_is_available(np)) {
		pr_err("%s: budget cooling is disable", __func__);
		return -EPERM;
	}

	budget_cdev = kzalloc(sizeof(*budget_cdev), GFP_KERNEL);
	if (IS_ERR_OR_NULL(budget_cdev)) {
		pr_err("cooling_dev: not enough memory for budget cooling data\n");
		return -ENOMEM;
	}

	if (of_property_read_u32(np, "cluster_num", &budget_cdev->cluster_num)) {
		pr_err("%s: get cluster_num failed", __func__);
		ret =  -EBUSY;
		goto parse_fail;
	}
	if (of_property_read_u32(np, "state_cnt", &budget_cdev->state_num)) {
		pr_err("%s: get state_cnt failed", __func__);
		ret =  -EBUSY;
		goto parse_fail;
	}

	ret = sunxi_budget_cooling_get_cpumask();
	if(ret)
		goto parse_fail;

	return 0;
parse_fail:
	kfree(budget_cdev);
	return ret;
}

static int sunxi_budget_cooling_probe(struct platform_device *pdev)
{
	s32 err = 0;
	struct thermal_cooling_device *cool_dev;

	pr_info("sunxi budget cooling probe start !\n");

	if (pdev->dev.of_node) {
		/* get dt and sysconfig */
		err = sunxi_budget_cooling_parse(pdev);
	}else{
		pr_err("sunxi budget cooling device tree err!\n");
		return -EBUSY;
	}
	if(err){
		pr_err("sunxi budget cooling device tree parse err!\n");
		return -EBUSY;
	}

	budget_cdev->dev = &pdev->dev;

	budget_cdev->cpufreq = sunxi_cpufreq_cooling_register(budget_cdev);
	budget_cdev->hotplug = sunxi_hotplug_cooling_register(budget_cdev);
	cool_dev = thermal_of_cooling_device_register(pdev->dev.of_node, SUNXI_BUDGET_COOLING_NAME,
						budget_cdev, &sunxi_cpu_cooling_ops);
	if (!cool_dev)
		goto fail;
	budget_cdev->cool_dev = cool_dev;
	budget_cdev->cooling_state = 0;
	dev_set_drvdata(&pdev->dev, budget_cdev);
	#ifdef USERSPACE_ROOMAGE
	device_create_file(&pdev->dev, &dev_attr_roomage);
	#endif

	pr_info("CPU budget cooling register Success\n");
	return 0;
fail:
	sunxi_cpufreq_cooling_unregister(budget_cdev);
	sunxi_hotplug_cooling_unregister(budget_cdev);
	kfree(budget_cdev);
	return -EBUSY;
}

static int sunxi_budget_cooling_remove(struct platform_device *pdev)
{
	thermal_cooling_device_unregister(budget_cdev->cool_dev);
	sunxi_cpufreq_cooling_unregister(budget_cdev);
	sunxi_hotplug_cooling_unregister(budget_cdev);;
	kfree(budget_cdev);
	pr_info("CPU budget cooling unregister Success\n");
	return 0;
}

#ifdef CONFIG_OF
/* Translate OpenFirmware node properties into platform_data */
static struct of_device_id sunxi_budget_cooling_of_match[] = {
	{ .compatible = "allwinner,budget_cooling", },
	{ },
};
MODULE_DEVICE_TABLE(of, sunxi_budget_cooling_of_match);
#else /* !CONFIG_OF */
#endif


static struct platform_driver sunxi_budget_cooling_driver = {
	.probe  = sunxi_budget_cooling_probe,
	.remove = sunxi_budget_cooling_remove,
	.driver = {
		.name   = SUNXI_BUDGET_DRIVER_NAME,
		.owner  = THIS_MODULE,
		.of_match_table = of_match_ptr(sunxi_budget_cooling_of_match),
	}
};

static int __init sunxi_budget_cooling_driver_init(void)
{
	return platform_driver_register(&sunxi_budget_cooling_driver);
}

static void __exit sunxi_budget_cooling_driver_exit(void)
{
	platform_driver_unregister(&sunxi_budget_cooling_driver);
}

subsys_initcall_sync(sunxi_budget_cooling_driver_init);
module_exit(sunxi_budget_cooling_driver_exit);
MODULE_DESCRIPTION("SUNXI budget cooling driver");
MODULE_AUTHOR("QIn");
MODULE_LICENSE("GPL v2");

